#include "tournament.h"
#include "dish.h"
#include "chef.h"
#include "judge.h"
#include "list.h"
#include "set.h"
#include <stdlib.h>
#include <string.h>

struct tournament_t {
	List judges;
	Set chefs;
};

//helper function, for Set of Chefs
static SetElement copyChefs(SetElement chef) {
	return chefCopy(chef,NULL);
}

//helper function, for Set of Chefs
static void freeChefs(SetElement chef) {
	chefDestroy(chef);
}

//helper function, for Set of Chefs
static int compareChefs(SetElement chef1, SetElement chef2) {
	return strcmp(chefGetName(chef1),chefGetName(chef2));
}

//helper function, for List of Judges
static ListElement copyJudge(ListElement judge) {
	return judgeCopy(judge,NULL);
}

//helper function, for List of Judges
static void freeJudge(ListElement judge) {
	judgeDestroy(judge);
}

/**
 * put a result code in a variable
 * @param result Address to put the code in. if NULL, does nothing.
 * @param code The result code to put in *result
 */
static void putResult(TournamentResult *result,TournamentResult code) {
	if (result) {
		*result=code;
	}
}

Tournament tournamentCreate(TournamentResult *result) {
	Tournament ret=malloc(sizeof(*ret));
	if (!ret) {
		putResult(result,TOURNAMENT_OUT_OF_MEMORY);
		return NULL;
	}
	ret->judges=listCreate(copyJudge,freeJudge);
	ret->chefs=setCreate(copyChefs,freeChefs,compareChefs);
	if (!ret->judges || !ret->chefs) {
		tournamentDestroy(ret);
		putResult(result,TOURNAMENT_OUT_OF_MEMORY);
		return NULL;
	}
	putResult(result,TOURNAMENT_SUCCESS);
	return ret;
}

Tournament tournamentCopy(Tournament tournament, TournamentResult *result) {
	if (!tournament) {
		putResult(result,TOURNAMENT_INVALID_ARGUMENT);
		return NULL;
	}
	Tournament ret=malloc(sizeof(*ret));
	if (!ret) {
		putResult(result,TOURNAMENT_OUT_OF_MEMORY);
		return NULL;
	}
	ret->judges=listCopy(tournament->judges);
	ret->chefs=setCopy(tournament->chefs);
	if (!ret->judges || !ret->chefs) {
		tournamentDestroy(ret);
		putResult(result,TOURNAMENT_OUT_OF_MEMORY);
		return NULL;
	}
	putResult(result,TOURNAMENT_SUCCESS);
	return ret;
}

void tournamentDestroy(Tournament tournament) {
	if (!tournament) {
		return;
	}
	listDestroy(tournament->judges);
	setDestroy(tournament->chefs);
	free(tournament);
}

void tournamentClear(Tournament tournament) {
	if (!tournament) {
		return;
	}
	listClear(tournament->judges);
	setClear(tournament->chefs);
}

TournamentResult tournamentAddChef(Tournament tournament, Chef chef) {
	if (!tournament || !chef) {
		return TOURNAMENT_INVALID_ARGUMENT;
	}
	switch(setAdd(tournament->chefs,chef)) {
	case SET_OUT_OF_MEMORY:
		return TOURNAMENT_OUT_OF_MEMORY;
		break;
	case SET_ITEM_ALREADY_EXISTS:
		return TOURNAMENT_CHEF_ALREADY_EXISTS;
		break;
	case SET_SUCCESS:
	default:
		return TOURNAMENT_SUCCESS;
		break;
	}
}

TournamentResult tournamentAddJudge(Tournament tournament, Judge judge) {
	if (!tournament || !judge) {
		return TOURNAMENT_INVALID_ARGUMENT;
	}
	switch(listInsertLast(tournament->judges,judge)) {
	case LIST_OUT_OF_MEMORY:
		return TOURNAMENT_OUT_OF_MEMORY;
		break;
	case LIST_SUCCESS:
	default:
		return TOURNAMENT_SUCCESS;
	}
}

Chef tournamentGetChef(Tournament tournament, const char *name) {
	if (!tournament || !name) {
		return NULL;
	}
	SET_FOREACH(Chef,chef,tournament->chefs) {
		if (strcmp(chefGetName(chef),name)==0) {
			return chef;
		}
	}
	return NULL;
}

Chef tournamentGetLeadingChef(Tournament tournament) {
	if (!tournament) {
		return NULL;
	}
	Chef leading=setGetFirst(tournament->chefs);
	if (!leading) {
		return NULL;
	}
	SET_FOREACH(Chef,chef,tournament->chefs) {
		if (chefGetScore(chef)>chefGetScore(leading) ||
				((chefGetScore(chef)==chefGetScore(leading)) &&
						compareChefs(chef,leading)<0)) {
			leading=chef;
		}
	}
	return leading;
} 

TournamentResult tournamentGetJudgesNames(Tournament tournament, List names) {
	if (!tournament || !names) {
		return TOURNAMENT_INVALID_ARGUMENT;
	}
	LIST_FOREACH(Judge,judge,tournament->judges) {
		if (listInsertLast(names,(char*)judgeGetName(judge))
				==LIST_OUT_OF_MEMORY) {
			return TOURNAMENT_OUT_OF_MEMORY;
		}
	}
	return TOURNAMENT_SUCCESS;
}

/**
 * Remove current Judge, and set internal iterator 
 * to the judge right after it
 */
static void removeCurrentJudge(List judges) {
	Judge current=listGetCurrent(judges),next=listGetNext(judges);
	if (!current || !judges) {
		return;
	}
	LIST_FOREACH(Judge,judge,judges) {
		if (judge==current) {
			break;
		}
	}
	listRemoveCurrent(judges);
	LIST_FOREACH(Judge,judge,judges) {
		if (judge==next) {
			break;
		}
	}
}

/**
 * Compete between dishes
 * Parameters: list of judges, 2 dishes,
 * 2 chefs, and list to add names of resigned judges to.
 * judges, dish1, chef1, dish2, and chef2 must not be NULL!
 * @return
 * 1 if chef1 won
 * -1 if chef2 won
 * 0 if tie
 * less than -1, if a memory allocation failed
 */
static int competeDish(List judges, Dish dish1, Chef chef1,
		Dish dish2, Chef chef2, List resignJudgesNames) {
	int remainJudges=listGetSize(judges);
	int diff=0;
	JudgeResult result;
	Judge judge=listGetFirst(judges);
	while (judge) {
		if (judgeCompareDishes(judge,dish1,chef1,dish2,chef2,&result)) {
			diff++;
		} else {
			diff--;
		}
		if (result==JUDGE_OUT_OF_MEMORY) {
			return -2;
		}
		if (judgeIsResigning(judge)) {
			switch (listInsertLast(resignJudgesNames,
					(char*)judgeGetName(judge))) {
			case LIST_OUT_OF_MEMORY:
				return -2;
				break;
			case LIST_SUCCESS:
			case LIST_NULL_ARGUMENT:
			default:
				break;
			}
			removeCurrentJudge(judges);
			judge=listGetCurrent(judges);
		} else {
			judge=listGetNext(judges);
		}
		remainJudges--;
		if (diff>remainJudges) {
			return 1;
		} else if (-diff>remainJudges) {
			return -1;
		}
	}
	return 0;
}

int tournamentCompeteChefs(Tournament tournament, Chef chef1, Chef chef2,
		List resignJudgesNames, TournamentResult *result) {
	if (!tournament || !chef1 || !chef2) {
		putResult(result,TOURNAMENT_INVALID_ARGUMENT);
		return 0;
	}
	if (compareChefs(chef1,chef2)==0) {
		putResult(result,TOURNAMENT_SAME_CHEF);
		return 0;
	}
	int diff=0;
	Dish dish1=chefGetFavoriteDish(chef1,NULL);
	Dish dish2=chefGetFavoriteDish(chef2,NULL);
	if (!dish1 || !dish2) {
		putResult(result,TOURNAMENT_CHEF_HAS_NO_DISHES);
		return 0;
	}
	do {
		if (listGetSize(tournament->judges)<=0) {
			putResult(result,TOURNAMENT_NO_JUDGES);
			return 0;
		}
		int point=competeDish(tournament->judges,dish1,chef1,
				dish2,chef2,resignJudgesNames);
		if (point<-1) {
			putResult(result,TOURNAMENT_OUT_OF_MEMORY);
			return 0;
		}
		diff+=point;
		chefRemoveFavoriteDish(chef1,NULL);
		chefRemoveFavoriteDish(chef2,NULL);
		dish1=chefGetFavoriteDish(chef1,NULL);
		dish2=chefGetFavoriteDish(chef2,NULL);
	} while (dish1 && dish2);
	putResult(result,TOURNAMENT_SUCCESS);
	if (diff>0) {
		chefIncrementScore(chef1);
	} else if (diff<0) {
		chefIncrementScore(chef2);
	}
	return diff;
}
